/**
 * 
 */
package com.suning.jiucai.core;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.Charset;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.net.SocketFactory;

import com.suning.jiucai.listener.Listener;

public abstract class AbstractConnection implements Connection {

	private final Logger log = Logger.getLogger(this.getClass().getName());
	
	private SocketFactory socketFactory;
	
	protected Socket socket;
	
	private Listener listener;
	
	protected class InitSocketException extends RuntimeException {
		private static final long serialVersionUID = 2389502693841442863L;

		public InitSocketException(String message) {
			super(message);
		}
	}
	private class CreateCommunicationPackException extends RuntimeException {
		private static final long serialVersionUID = 2389502693841442863L;
		
		public CreateCommunicationPackException(Throwable cause) {
			super(cause);
		}
	}
	
	private Dock dock;
	
	public AbstractConnection(Dock dock) {
		this.dock = dock;
	}
	
	protected abstract Node getNode(Dock dock);
	
	protected abstract void handshake(Node node);
	
	Thread listenerThread;
	
	@Override
	public void setRecivedListener(Listener listener) {
		this.listener = listener;
	}
	
	private Socket getSocket() {
		if (null == socket || socket.isClosed() || !socket.isConnected()) {
			try {
			 	Node node = getNode(dock);
			 	socket = getSocktFactory().createSocket(node.getIp(), node.getPort());
			 	socket.setKeepAlive(true);
			 	socket.setTcpNoDelay(true);
				handshake(node);
				listenerThread = new Thread(new Runnable() {
					@Override
					public void run() {
						invokeRecievedListener();
					}
				});
				listenerThread.start();
			} catch (Exception e) {
				log.log(Level.WARNING, "Connect to jiucai server error. e:" + e);
				throw new InitSocketException(e.getMessage());
			}
		} 
		return socket;
	}
	
	private SocketFactory getSocktFactory() {
		if (null == socketFactory) {
			socketFactory = SocketFactory.getDefault();
		}
		return socketFactory;
	}
	
	public void sendMsg(String json) {
		try {
			Socket socket = getSocket();
			if (null == socket || socket.isClosed() || !socket.isConnected()) {
				log.log(Level.WARNING, "No avaliable socket.");
				return;
			}
			socket.getOutputStream().write(getPack(json));
			socket.getOutputStream().flush();
		} catch (IOException e) {
		} 
	}
	
	protected byte[] getPack(String json) {
		ByteArrayOutputStream bao = null;
		try {
			bao = new ByteArrayOutputStream();
			ByteBuffer head = ByteBuffer.allocate(4);
			head.putInt(json.getBytes(Charset.forName("UTF-8")).length);
			bao.write(head.array());
			bao.write(json.getBytes(Charset.forName("UTF-8")));
			bao.flush();
			final byte[] bytes = bao.toByteArray();
			return bytes;
		} catch (Exception e) {
			throw new CreateCommunicationPackException(e);
		} finally {
			if (null != bao) {
				try {
					bao.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	protected void invokeRecievedListener() {
		try {
			InputStream in = socket.getInputStream();
			ReadableByteChannel reChannel = Channels.newChannel(in);
			ByteBuffer byteBuffer = ByteBuffer.allocate(1024*1024*2);
			while(reChannel.read(byteBuffer) != -1) {
				// 保存bytebuffer状态
		        int position = byteBuffer.position();
		        int limit = byteBuffer.limit();
		        byteBuffer.flip();
		        // 判断数据长度是否够首部长度
		        if (byteBuffer.remaining() < 4) {
		            continue;
		        }
		        // 判断bytebuffer中剩余数据是否足够一个包
		        int length = byteBuffer.getInt();
		        if (byteBuffer.remaining() < length) {
		            byteBuffer.position(position);
		            byteBuffer.limit(limit);
		            continue;
		        }
		        // 拿到实际数据包
		        byte[] data = new byte[length];

		        byteBuffer.get(data, 0, length);
		        //未被读取的要保留 用 compact
		        byteBuffer.compact();
		        System.out.println(new String(data));
		        if (null != listener) {
		        	listener.recieved(new String(data));
		        } 
			}
		} catch (IOException e) {
			log.log(Level.WARNING, "读取消息服务器消息异常。 e:" + e);
		}
	}
	
}
